﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
using System.Globalization;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;

namespace DAValidation
{
	public class DataAnnotationsValidator : BaseValidator
	{
		public const string DAValidationScriptFileName = "DAValidation.DAValidation.js";
		private const string MetadataSourceViewStateKey = "DataAnnotationsValidator_MetadataSourceID";

		[System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1709:IdentifiersShouldBeCasedCorrectly", MessageId = "ID", Justification = "This should be named with ID at the end, because it is keeping consistency in naming.")]
		[Category("Behavior")]
		[Themeable(false)]
		[DefaultValue("")]
		[IDReferenceProperty(typeof(MetadataSource))]
		public string MetadataSourceID
		{
			get
			{
				return Convert.ToString(ViewState[MetadataSourceViewStateKey], CultureInfo.InvariantCulture);
			}
			set
			{
				ViewState[MetadataSourceViewStateKey ] = value;
			}
		}

		private MetadataSource MetadataSource 
		{
			get; set;
		}

		[Category("Behavior")]
		[Themeable(false)]
		[DefaultValue("")]
		public string ObjectProperty { get; set; }

		private IEnumerable<ValidationAttribute> ValidationAttributes { get; set; }
		private string DisplayName { get; set; }

		protected override void OnLoad(EventArgs e)
		{
			base.OnLoad(e);

			if (!ControlPropertiesValid())
				return;

			MetadataSource = this.FindChildControl<MetadataSource>(MetadataSourceID);

			ValidationAttributes = MetadataSource.GetValidationAttributes(ObjectProperty);
			DisplayName = MetadataSource.GetDisplayName(ObjectProperty);
		}

		protected override bool EvaluateIsValid()
		{
			object value = GetControlValidationValue(ControlToValidate);
			foreach (ValidationAttribute validationAttribute in ValidationAttributes)
			{
				// Here, we will try to convert value to type specified on RangeAttibute. 
				// RangeAttribute.OperandType should be either IConvertible or of built in primitive types
				var rangeAttibute = validationAttribute as RangeAttribute;
				if (rangeAttibute != null)
				{
					value = Convert.ChangeType(value, rangeAttibute.OperandType, CultureInfo.CurrentCulture);
				}

				if (validationAttribute.IsValid(value)) continue;
			
				ErrorMessage = validationAttribute.FormatErrorMessage(DisplayName);
				return false;
			}

			return true;
		}

		protected override bool ControlPropertiesValid()
		{
			if (string.IsNullOrEmpty(MetadataSourceID))
				throw new HttpException("MetadataSourceID property cannot be blank");

			return base.ControlPropertiesValid();
		}

		protected override void AddAttributesToRender(HtmlTextWriter writer)
		{
			if (RenderUplevel)
			{
				this.RegisterExpandoAttribute("evaluationfunction", "DataAnnotationsValidatorIsValid");

				var validationRules = ValidationAttributes.SelectMany(attribute => ClientValidationRulesProvider.GetClientValidationRules(attribute, DisplayName));
				var errorMessages = ValidationAttributes.Select(attribute => attribute.FormatErrorMessage(DisplayName));

				var validatorFunctions = new List<string>();
				foreach (var rule in validationRules)
				{
					validatorFunctions.Add(rule.EvaluationFunction);
					foreach (var ruleParameter in rule.Parameters)
					{
						this.RegisterExpandoAttribute(ruleParameter.Key, Convert.ToString(ruleParameter.Value, CultureInfo.InvariantCulture));
					}
				}

				this.RegisterExpandoAttribute("validatorFunctions", string.Join(";;", validatorFunctions));
				this.RegisterExpandoAttribute("errorMessages", string.Join(";;", errorMessages));
				this.RegisterExpandoAttribute("supressErrorMessageText", Text.Trim().Length > 0 ? "true" : "false");
			}

			base.AddAttributesToRender(writer);
		}

		protected override void OnPreRender(EventArgs e)
		{
			base.OnPreRender(e);

			if (RenderUplevel)
			{
				var scriptManager = ScriptManager.GetCurrent(Page);
				if (scriptManager != null && scriptManager.IsInAsyncPostBack)
					ScriptManager.RegisterClientScriptResource(this, GetType(), DAValidationScriptFileName);
				else
					Page.ClientScript.RegisterClientScriptResource(GetType(), DAValidationScriptFileName);
			}
		}
	}
}